#pragma once

#include<iostream>
#include<string>
#include<memory>
#include<cerrno>
#include<functional>
#include<unordered_map>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include"Socket.hpp"
#include"Epoller.hpp"
#include"SetNoBlock.hpp"

using namespace std;

class Connection;
class TcpServer;

static const int g_buffer_size = 1024;
static const int num = 64;//设置EPOLL总共可以关注的文件描述符的数量
uint32_t EVENT_IN = EPOLLIN | EPOLLET;//采用ET的方式
uint32_t EVENT_OUT = EPOLLOUT | EPOLLET;

//我们自己定义回调函数
using func_t = function<void (shared_ptr<Connection>)>;

//我们手动维护一个Connection对象，把输入和输出缓冲区定义在里面
//也就是说用一个文件描述符来维护一个输入缓冲区和输出缓冲区，而不是向之前一样直接在栈上共用char buffer[1024]这种bug多
class Connection
{
public:
    Connection(int sock, shared_ptr<TcpServer> tcp_server_ptr)
    :_sock(sock),
    _tcp_server_ptr(tcp_server_ptr),
    _recv_cb(nullptr),
    _send_cb(nullptr),
    _except_cb(nullptr)
    { }

    void SetHander(func_t recv_cb, func_t send_cb, func_t except_cb)
    {
        _recv_cb = recv_cb;
        send_cb = _send_cb;
        except_cb = _except_cb;
    }

    void AppendInbuffer(const string& info)
    {
        _inbuffer += info;
    }

    void AppendOutbuffer(const string& info)
    {
        _outbuffer += info;
    }

    int SockFd() {return _sock;}

    const string& GetInbuffer() {return _inbuffer;}
    string& GetOutBUffer() {return _outbuffer;}

    ~Connection()
    {

    }
private:
    int _sock;

    string _inbuffer;//输入缓冲区
    string _outbuffer;//输出缓冲区
public:
    func_t _recv_cb;//读取的回调函数
    func_t _send_cb;//写入的回调函数
    func_t _except_cb;//异常的回调函数

    shared_ptr<TcpServer> _tcp_server_ptr;
public:
    string ip;
    uint16_t port;
};


class TcpServer
{
public:
    TcpServer(uint16_t port, func_t OnMessage)
    :_port(port),
    //_OnMessage(OnMessage),
    _Listenock_ptr(new Sock()),
    _epoller_ptr(new Epoller())
    {
        
    }

    //添加文件描述符, 该文件描述符所关心的事件, 读/写/异常 回调函数到我们自己维护的Connection中
    void AddConnection(int sock, uint32_t events, func_t recv_cb, func_t send_cb, func_t except_cb,
     string clientip = "0.0.0.0", uint16_t clientport = 8080)
    {
        //1.我们将文件描述符sock和Connection对象联系起来
        //给sock创建Connection对象, 将sock添加到Connection中m, 并且把用回调函数对Connection进行初始化
        std::shared_ptr<Connection> new_connection = make_shared<Connection>(sock, shared_ptr<TcpServer>(this));
        new_connection->SetHander(recv_cb, send_cb, except_cb);
        new_connection->ip = clientip;
        new_connection->port = clientport;

        //2.把sock和Connection添加到哈希表中关联起来
        _Connections.insert({sock, new_connection});

        // 3.我们讲sock和EVENT添加到内核的Epoll模型中
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, sock, events);
    }

    void Init()
    {
        _Listenock_ptr->Socket();
        SetNoBlock(_Listenock_ptr->Fd());//把listensock设置成为非阻塞, ET状态一定要是非阻塞的
        _Listenock_ptr->Bind(_port);
        _Listenock_ptr->Listen();

        // AddConnection(_Listenock_ptr->Fd(), EVENT_IN,
        // std::bind(&TcpServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);

        //我个人觉得bind的原因是调整参数的个数, 因为func_t是void (shared_ptr<Connection>)类型
        //但是void Accpet(shared_ptr<Connection>)因为他是成员函数, 所以他有this指针，故他其实有两个参数, 所以需要bind来调整参数的个数
        AddConnection(_Listenock_ptr->Fd(), EVENT_IN, bind(&TcpServer::Accepter, this, placeholders::_1), nullptr, nullptr);

        lg(Info, "Create sock fd success, sock fd:%d", _Listenock_ptr->Fd());
    }

    void Recver(shared_ptr<Connection> connection)
    {
        int sock = connection->SockFd();

        while(true)
        {
            char buffer[g_buffer_size];
            memset(buffer, 0, sizeof(buffer));
            ssize_t n = recv(sock, buffer, sizeof(buffer), 0);//这里写0是因为前面已经设置成为了非阻塞
            if(n > 0)
            {
                //一直读取, 并且往connection里面添加
                connection->AppendInbuffer(buffer);
            }
            else if(n == 0)
            {
                //知道哪一个客户端退出
                lg(Info, "sockfd: %d, client info %s:%d quit...", sock, connection->ip.c_str(), connection->port);
                connection->_except_cb(connection);
            }
            else//异常
            {
                //读取出错有多种情况需要处理
                if(errno == EWOULDBLOCK)    break;
                else if(errno == EINTR)     continue;
                else
                {
                    lg(Warning, "sockfd: %d, client info %s:%d recv error...", sock, connection->ip.c_str(), connection->port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }

        //_OnMessage(connection);//把数据交给上层，服务器只负责完整的发送和接收数据，至于如何处理是应用层的事情
    }

    //对读写事件设置关心
    void EnableRW(int sock, bool readable, bool writeable)
    {
        uint32_t events = 0;
        events |= (readable ? EPOLLIN : 0);
        events |= (writeable ? EPOLLIN : 0);
        events |= EPOLLET;
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_MOD, sock, events);//这里用EPOLL_CTL_MOD表示修改
    }

    //正确的理解发送
    //对于写事件我们不必一直开启对写事件的关心, 因为一般情况下写事件常常是就绪的, 只有当我们发送的时候循环发送最后发现输出缓冲区仍然有数据的时候
    //这个时候表示写事件不就绪，我们就需要开启对写事件的关心, 当我们发现能够正常的发送的时候我们把写实间关心关闭
    void Sender(shared_ptr<Connection> connection)
    {
        int sock = connection->SockFd();
        auto& outbuffer = connection->GetOutBUffer();

        while(true)
        {
            ssize_t n = send(sock, outbuffer.c_str(), outbuffer.size(), 0);
            if(n > 0)
            {
                outbuffer.erase(0, n);
                if(outbuffer.empty())
                    break;
            }
            else if(n == 0)
            {
                return;
            }
            else
            {
                if(errno == EWOULDBLOCK)
                    break;
                else if(errno == EINTR)
                    continue;
                else
                {
                    lg(Warning, "sock fd: %d, clinet info-> [%s:%d] sent error...", sock, connection->ip.c_str(), connection->port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }

        //1.因为缓冲区满了导致数据没发完 2.发送缓冲区为空，整体发完了
        if(!outbuffer.empty())
        {
            //走到这里表示我们一直发送经过不断的循环发送之后发送缓冲区仍然有数据
            //这个时候我们再开启对写事件的关心
            EnableRW(sock, true, true);
        }
        else
        {
            //这次发送的时候把缓冲区的数据发完了，我们再关闭对写事件的关心
            EnableRW(sock, true, false);
        }
    }

    void Excepter(shared_ptr<Connection> connection)
    {
        int sock = connection->SockFd();

        lg(Debug, "Excepter hander sockfd: %d, client info %s:%d excepter hander", sock, connection->ip.c_str(), connection->port);
        if(!IsConnectionSafe(sock))//不安全直接返回
            return;

        //1.在EPOLL中移除对事件的关心
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, sock, 0);

        //2.关闭异常的文件描述符
        close(sock);
        lg(Debug, "sock: %d is done...", sock);

        //3.在哈希表中把该节点移除, connection对象我们可以不用手动释放, 因为智能指针自动释放空间
        _Connections.erase(sock);
        lg(Debug, "sock: %d remove from _Connections...", sock);
    }

    //处理事件：设置一个获取链接的回调函数, 未来我们添加listensock时，他的读回调就不能设置成nullptr而是设置成该函数
    void Accepter(shared_ptr<Connection> connection)
    {
        //处于ET模式需要一直读，我们直到它读出错的时候进行判断就可以知道是否读取完全
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            int sock = ::accept(connection->SockFd(), (struct sockaddr*)&peer, &len);
            if(sock > 0)
            {
                //我想要获取客户端的ip和port
                uint16_t clientport = ntohs(peer.sin_port);
                char ipbuf[128];
                inet_ntop(AF_INET, &peer.sin_addr.s_addr, ipbuf, sizeof(ipbuf));
                lg(Debug, "get a new link, get info-> [%s:%d], sockfd: %d", ipbuf, clientport, sock);

                SetNoBlock(sock);
                //我们的litensock只需要处理读取回调，但是其他的fd需要处理读写和异常，也就是都需要处理
                AddConnection(sock, EVENT_IN,
                std::bind(&TcpServer::Recver, this, std::placeholders::_1),
                std::bind(&TcpServer::Sender, this, std::placeholders::_1),
                std::bind(&TcpServer::Excepter, this, std::placeholders::_1),
                ipbuf, clientport);
            }
            else
            {
                if(errno == EWOULDBLOCK)//说明已经读完了
                    break;
                else if(errno == EINTR)//被信号中断阻塞 
                     continue;
                else//读取出错
                    break;
            }
        }
    }

    //判断我们的fd是不是在_connections中
    bool IsConnectionSafe(int fd)
    {
        auto iter = _Connections.find(fd);

        if(iter == _Connections.end())
        {
            return false;
        }

        return true;
    }

    void Dispatcher(int timeout)
    {
        int n = _epoller_ptr->EpollerWait(recvs, num, timeout);
        for(int i = 0; i < n; i++)//这里一定要写n
        {
            uint32_t events = recvs[i].events;
            int sock = recvs[i].data.fd;

            //这里我们希望的是如果遇到异常情况, 我们把它放到读和写的常规情况中处理
            if(events & EPOLLERR)
                events |= (EPOLLIN | EPOLLOUT);
            
            if(events & EPOLLHUP)
                events |= (EPOLLIN | EPOLLOUT);

            if((events & EPOLLIN) && IsConnectionSafe(sock))//处理读事件, 这里我们需要判断sock是否在我们维护的_connections中
            {
                //这里表示的是如果Connection中的处理读事件的回调函数存在，也就是被初始化了，那么就执行该函数
                if(_Connections[sock]->_recv_cb)
                {
                    _Connections[sock]->_recv_cb( _Connections[sock]);
                }
            }

            if((events & EPOLLOUT) && IsConnectionSafe(sock))//处理写事件
            {
                if(_Connections[sock]->_send_cb)
                {
                    _Connections[sock]->_send_cb( _Connections[sock]);
                }
            }
        }
    }

    void Loop()
    {
        while(true)
        {
            Dispatcher(3000);
            PrintConnection();
        }
    }

    void PrintConnection()
    {
        cout << "Connections fd list:";
        for(auto& connection : _Connections)
        {
            cout << connection.second->SockFd() << ", ";
            cout << "inbuffer: " << connection.second->GetInbuffer();
        }
        cout << endl;
    }

    ~TcpServer()
    {}
private:
    shared_ptr<Sock> _Listenock_ptr;
    shared_ptr<Epoller> _epoller_ptr;
    struct epoll_event recvs[num];
    unordered_map<int, shared_ptr<Connection>> _Connections;
    
    int _timeout = 3000;
    uint16_t _port;

    // func_t _OnMessage;
};